﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Minecraft.WorldMap
{
	internal sealed class SectorizedStream : System.IO.Stream
	{
		internal System.IO.Stream BaseStream { get; private set; }
		byte[] sectorData;
		int currentSector;
		int lastReadSector;
		long position;

		internal SectorizedStream(System.IO.Stream baseStream, int sectorSize)
		{
			BaseStream = baseStream;
			sectorData = new byte[sectorSize];
			currentSector = -1;
			lastReadSector = -1;
			position = 0;
		}

		public override bool CanRead
		{
			get { return BaseStream.CanRead; }
		}

		public override bool CanSeek
		{
			get { return BaseStream.CanSeek; }
		}

		public override bool CanWrite
		{
			get { return false; }
		}

		public override void Flush()
		{
			BaseStream.Flush();
		}

		public override long Length
		{
			get { return BaseStream.Length; }
		}

		internal int Sectors
		{
			get { return (int)(BaseStream.Length / sectorData.Length); }
		}

		public override long Position
		{
			get
			{
				return position;
			}
			set
			{
				if (!BaseStream.CanSeek)
					throw new NotSupportedException();
				if (value >= BaseStream.Length)
				{
					currentSector = (int)(value / sectorData.Length);
					BaseStream.Position = BaseStream.Length;
					Array.Clear(sectorData, 0, sectorData.Length);
					position = value;
				}
				else if (SetSector((int)(value / sectorData.Length)))
				{
					position = value;
				}
			}
		}

		internal int Sector
		{
			get { return currentSector; }
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			if (((position % sectorData.Length) == 0) && (currentSector == lastReadSector))
				if (!ReadNextSector())
					return 0;
			lastReadSector = currentSector;
			if (((position % sectorData.Length) + count) > sectorData.Length)
				count = (int)(sectorData.Length - (position % sectorData.Length));
			Array.Copy(sectorData, position % sectorData.Length, buffer, offset, count);
			position += count;
			return count;
		}

		private bool ReadNextSector()
		{
#if DEBUG
			if (NamedBinaryTag.DebugFlags.GetDebug(4))
				Console.WriteLine("ReadNextSector: Current = {0}", currentSector);
#endif
			int bufferPosition = 0;
			int read = 0;
			while (bufferPosition < sectorData.Length)
			{
				read = BaseStream.Read(sectorData, bufferPosition, (int)(sectorData.Length - bufferPosition));
				bufferPosition += read;
				if (read == 0)
				{
					Array.Clear(sectorData, bufferPosition, sectorData.Length - bufferPosition);
					break;
				}
			}
			if (bufferPosition == 0)
			{
#if DEBUG
				if (NamedBinaryTag.DebugFlags.GetDebug(4))
					Console.WriteLine("ReadNextSector: EOF");
#endif
				return false;
			}
			currentSector++;
#if DEBUG
			if (NamedBinaryTag.DebugFlags.GetDebug(4))
				Console.WriteLine("ReadNextSector: New = {0}", currentSector);
#endif
			return true;
		}

		internal bool SkipToSector(int sector)
		{
#if DEBUG
			if (NamedBinaryTag.DebugFlags.GetDebug(4))
				Console.WriteLine("SkipToSector: Target = {0}", sector);
#endif
			if (sector < currentSector)
				return false;
			while (sector > currentSector)
				ReadNextSector();
			position = sector * sectorData.LongLength;
			return true;
		}

		public override long Seek(long offset, System.IO.SeekOrigin origin)
		{
			if (!BaseStream.CanSeek)
				throw new NotSupportedException("Not seekable");
			long newPosition = 0;
			switch (origin)
			{
				case System.IO.SeekOrigin.Begin:
					newPosition = offset;
					break;
				case System.IO.SeekOrigin.Current:
					newPosition = offset + position;
					break;
				case System.IO.SeekOrigin.End:
					newPosition = BaseStream.Length + offset;
					break;
			}
			if (newPosition < 0)
			{
				newPosition = 0;
			}
			if (newPosition >= BaseStream.Length)
			{
				currentSector = (int)(newPosition / sectorData.Length);
				BaseStream.Position = BaseStream.Length;
				Array.Clear(sectorData, 0, sectorData.Length);
				position = newPosition;
			}
			else if (SetSector((int)(newPosition / sectorData.Length)))
			{
				position = newPosition;
			}
			return newPosition;
		}

		internal bool SetSector(int sector)
		{
			if (this.currentSector < sector)
				return SkipToSector(sector);
			else if (this.currentSector == sector)
			{
				position = sector * sectorData.Length;
				return true;
			}
			if (!BaseStream.CanSeek || (sector < 0))
				throw new InvalidOperationException("Not seekable");
			long newPosition = sector * sectorData.Length;
			if (newPosition >= BaseStream.Length)
			{
				return false;
			}
			this.currentSector = sector - 1;
			BaseStream.Position = newPosition;
			ReadNextSector();
			position = newPosition;
			return true;
		}

		public override void SetLength(long value)
		{
			throw new NotSupportedException();
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotSupportedException();
		}
	}
}
